package com.aurora.leetcode.multithreading;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.IntConsumer;

/**
 * @author : qiaodan
 * @date : 2024/9/3 17:45
 * @description 1116. 打印零与奇偶数
 * <p>
 * 现有函数 printNumber 可以用一个整数参数调用，并输出该整数到控制台。
 *</p>
 * <p>
 * 例如，调用 printNumber(7) 将会输出 7 到控制台。
 * 给你类 ZeroEvenOdd 的一个实例，该类中有三个函数：zero、even 和 odd 。ZeroEvenOdd 的相同实例将会传递给三个不同线程：
 * 线程 A：调用 zero() ，只输出 0
 * 线程 B：调用 even() ，只输出偶数
 * 线程 C：调用 odd() ，只输出奇数
 * 修改给出的类，以输出序列 "010203040506..." ，其中序列的长度必须为 2n 。
 * 实现 ZeroEvenOdd 类：
 * ZeroEvenOdd(int n) 用数字 n 初始化对象，表示需要输出的数。
 * <p>
 * void zero(printNumber) 调用 printNumber 以输出一个 0 。
 * </p>
 * <p>
 * void even(printNumber) 调用printNumber 以输出偶数。
 * </p>
 * <p>
 * void odd(printNumber) 调用 printNumber 以输出奇数。
 * </p>
 * <p>
 * 示例 1：
 * </p>
 * <p>
 * 输入：n = 2
 * </p>
 * <p>
 * 输出："0102"
 * </p>
 * <p>
 * 解释：三条线程异步执行，其中一个调用 zero()，另一个线程调用 even()，最后一个线程调用odd()。正确的输出为 "0102"。
 * </p>
 * <p>
 * 示例 2：
 * </p>
 * <p>
 * 输入：n = 5
 * </p>
 * <p>
 * 输出："0102030405"
 * </p>
 */

public class ZeroEvenOdd_1116 {
    private int n;

    public ZeroEvenOdd_1116(int n) {
        this.n = n;
    }
    AtomicInteger num = new AtomicInteger(1);
    ReentrantLock lock = new ReentrantLock(true);
    Condition zeroCd = lock.newCondition();
    Condition evenCd = lock.newCondition();
    Condition oddCd = lock.newCondition();
    volatile boolean needZero = true;
    // printNumber.accept(x) outputs "x", where x is an integer.

    public void zero1(IntConsumer printNumber) {
        while(true){
            lock.lock();
            try{
                zeroCd.await();
                if(!needZero) break;
                printNumber.accept(0);
                if(num.get()%2==0){
                    evenCd.signal();
                }else{
                    oddCd.signal();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }
    }
    public void even1(IntConsumer printNumber)  {
        while(true){
            lock.lock();
            try{
                if(num.get() > n) {
                    needZero = false;
                    zeroCd.signal();
                    oddCd.signal();
                    break;
                }
                if(num.get() % 2 == 0){
                    zeroCd.signal();
                    evenCd.await();
                    printNumber.accept(num.getAndIncrement());

                }else{
                    oddCd.signal();
                    evenCd.await();
                }

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }
    }
    public void odd1(IntConsumer printNumber)  {
        while(true){
            lock.lock();
            try{
                if(num.get() > n) {
                    needZero = false;
                    zeroCd.signal();
                    evenCd.signal();
                    break;
                }
                if(num.get() % 2 == 1){
                    zeroCd.signal();
                    oddCd.await();
                    printNumber.accept(num.getAndIncrement());
                }else{
                    evenCd.signal();
                    oddCd.await();
                }

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }
    }


    /**
     * 参考方法 lock+condition
     */

    Condition condition = lock.newCondition();
    volatile int flag = 1;
    public void zero(IntConsumer printNumber) {
        for(int i=1; i<=n; ++i){
            lock.lock();
            try{
                printNumber.accept(0);
                flag ++;
                condition.signalAll();
                while(flag % 2 == 0) condition.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }finally {
                lock.unlock();
            }
        }
    }
    public void even(IntConsumer printNumber) {
        for(int i=2; i<=n; i+=2){
            lock.lock();
            try{
                while(flag % 4 != 0) condition.await();
                printNumber.accept(i);
                flag ++;
                condition.signalAll();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }finally {
                lock.unlock();
            }
        }
    }
    public void odd(IntConsumer printNumber) {
        for(int i=1; i<=n; ++i){
            lock.lock();
            try{
                while(flag % 4 != 2) condition.await();
                printNumber.accept(i);
                flag ++;
                condition.signalAll();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }finally {
                lock.unlock();
            }
        }
    }


    public static void main(String[] args) {
        ZeroEvenOdd_1116 obj = new ZeroEvenOdd_1116(100);
        IntConsumer intConsumer = new IntConsumer() {
            @Override
            public void accept(int value) {
                System.out.print(value);
            }
        };
        CountDownLatch cdl = new CountDownLatch(3);
        CompletableFuture.runAsync(new Runnable() {
            @Override
            public void run() {
                obj.zero(intConsumer);
            }
        }).whenComplete((res, Exp)->{
            cdl.countDown();
        });

        CompletableFuture.runAsync(new Runnable() {
            @Override
            public void run() {
                obj.even(intConsumer);
            }
        }).whenComplete((res, Exp)->{
            cdl.countDown();
        });
        CompletableFuture.runAsync(new Runnable() {
            @Override
            public void run() {
                obj.odd(intConsumer);
            }
        }).whenComplete((res, Exp)->{
            cdl.countDown();
        });
        try {
            cdl.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }


    }
}
